package com.example.OnlineStorage.pojo.entity;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.Getter;
import lombok.Setter;

import java.math.BigInteger;
import java.util.*;

/**
 * 节点类，作为文件系统或目录结构中的基本单元。
 * 它可以是文件或目录，具体由子类实现确定。
 */
@Getter
@Setter
public abstract class Node {
	protected String name; // 节点名称
	protected String path; // 节点路径
	protected String type; // 节点类型
	protected Map<String, Node> children; // 子节点集合
	protected String SHA_256; // 文件的SHA-256散列值，用于文件校验
	protected BigInteger size;
	
	/**
	 * 构造函数，初始化节点的基本属性。
	 *
	 * @param name     节点名称
	 * @param path     节点路径
	 * @param type     节点类型
	 * @param SHA_256  节点的SHA-256散列值
	 * @param children 子节点的映射
	 */
	public Node (String name, String path, String type, String SHA_256, Map<String, Node> children, BigInteger size) {
		this.name = name;
		this.path = path;
		this.type = type;
		this.SHA_256 = SHA_256;
		this.children = children;
		this.size = size;
	}
	
	/**
	 * 创建一个新的子节点。根据指定的名称、散列值、是否是目录以及类型，决定创建文件还是目录。
	 *
	 * @param name        子节点的名称
	 * @param SHA_256     子节点的SHA-256散列值
	 * @param isDirectory 指示是否创建目录
	 * @param type        节点的类型
	 * @ 返回创建之后的path
	 */
	public String createChild (String name, String SHA_256, boolean isDirectory, String type, BigInteger size) {
		int i = 1;
		String savePath = this.path + name;
		if (this.children.containsKey(name)) {
			return createChild(name, SHA_256, isDirectory, type, size, i);
		}
		if (isDirectory) {
			savePath += "/";
			this.children.put(name, new Directory(name, savePath));
			return savePath;
		} else {
			this.children.put(name, new File(name, savePath, SHA_256, type, size));
			return savePath;
		}
	}
	
	private String createChild (String name, String SHA_256, boolean isDirectory, String type, BigInteger size, int i) {
		String savePath = this.path + name + "(" + i + ")";
		if (this.children.containsKey(name + "(" + i + ")")) {
			return createChild(name, SHA_256, isDirectory, type, size, i + 1);
		}
		if (isDirectory) {
			savePath += "/";
			this.children.put(name + "(" + i + ")", new Directory(name + "(" + i + ")", savePath));
			return savePath;
		} else {
			this.children.put(name, new File(name + "(" + i + ")", savePath, SHA_256, type, size));
			return savePath;
		}
	}
	
	/**
	 * 根据路径获取子节点。递归搜索子节点，直到找到匹配路径的节点。
	 *
	 * @param path 节点的路径
	 * @return 匹配路径的子节点，如果不存在，则返回null。
	 */
	public Node getChildByPath (String path) {
		Node node = null;
		if (path.equals("/")) {
			return this;
		}
		String[] pathArray = path.split("/");
		Node current = this;
		for (String s : pathArray) {
			if (s.isEmpty()) {
				continue;
			}
			node = current.children.get(s);
			if (node == null) {
				break;
			}
			current = node;
		}
		return node;
	}
	
	/**
	 * 创建一个文件夹数组_从自身去创建
	 *
	 * @param Dir 文件夹数组
	 * @return 返回创建到哪个位置的节点
	 */
	public Node createDirectoryArray (String[] Dir) {
		Node node = this;
		for (int i = 0; i < Dir.length - 1; i++) {
			String savePath = node.createChild(Dir[i], null, true, "Directory", null);
			node = node.getChildByPath(savePath);
		}
		return node;
	}
	
	/**
	 * 创建一个文件夹数组_从指定路径去创建
	 *
	 * @param fromPath 从哪个路径下面创建
	 * @param Dir      文件夹数组
	 * @return 返回创建到哪个位置的节点
	 */
	public Node createDirectoryArray (String fromPath, String[] Dir) {
		Node node = fromPath.equals("/") ? this : this.getChildByPath(fromPath);
		if (node == null) {
			return null;
		}
		for (int i = 0; i < Dir.length - 1; i++) {
			String savePath = node.createChild(Dir[i], null, true, "Directory", null);
			node = node.getChildByPath(savePath);
		}
		return node;
	}
	
	/**
	 * 递归查找 判断HA-256是否存在
	 *
	 * @param SHA_256 节点的SHA-256散列值
	 * @return 如果存在返回 true，否则返回 false
	 */
	public boolean isFileSHA_256Exist (String SHA_256) {
		Stack<Node> stack = new Stack<>();
		stack.push(this);
		while (! stack.isEmpty()) {
			Node node = stack.pop();
			if (node.isDirectory()) {
				for (Map.Entry<String, Node> entry : node.children.entrySet()) {
					stack.push(entry.getValue());
				}
			} else {
				if (node.SHA_256.equals(SHA_256)) {
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * 清空所有子节点。
	 *
	 * @return 返回true，表示子节点已被清空。
	 */
	public boolean removeChild () {
		this.children.clear();
		return true;
	}
	
	/**
	 * 获取节点的大小
	 *
	 * @return 返回节点的大小
	 */
	public BigInteger getNodeSize () {
		BigInteger size = BigInteger.ZERO;
		
		Stack<Node> stack = new Stack<>();
		stack.push(this);
		while (! stack.isEmpty()) {
			Node node = stack.pop();
			if (node.isDirectory()) {
				for (Map.Entry<String, Node> entry : node.children.entrySet()) {
					stack.push(entry.getValue());
				}
			} else {
				size = size.add(node.getSize());
			}
		}
		return size;
	}
	
	/**
	 * 获取节点的所有SHA-256
	 *
	 * @return 返回节点的所有SHA-256
	 */
	public List<String> getAllSha256 () {
		List<String> sha256List = new ArrayList<>();
		if (this.isDirectory()) {
			for (Map.Entry<String, Node> entry : this.children.entrySet()) {
				if (entry.getValue().isDirectory()) {
					sha256List.addAll(entry.getValue().getAllSha256());
				} else {
					sha256List.add(entry.getValue().getSHA_256());
				}
			}
		} else {
			sha256List.add(this.SHA_256);
		}
		return sha256List;
	}
	
	/**
	 * 获取父节点的路径
	 */
	public String getParentPath () {
		if (Objects.equals(this.getPath(), "/")) {
			return this.getPath();
		}
		if (this.isDirectory()) {
			// "/Test/dist/"
			String parentPath = this.getPath().substring(0, this.getPath().lastIndexOf("/"));
			return parentPath.substring(0, parentPath.lastIndexOf("/") + 1);
		} else {
			// "/Test/index.js"
			return this.getPath().substring(0, this.getPath().lastIndexOf("/") + 1);
		}
	}
	
	/**
	 * 根据路径删除子节点。递归搜索子节点，直到找到匹配路径的节点并删除。
	 *
	 * @param path 节点的路径
	 * @return 如果成功删除子节点，则返回true；如果不存在匹配路径的节点，则返回false。
	 */
	public boolean removeChildByPath (String path) {
		if (path.equals("/")) {
			return false;
		}
		if (path.equals(this.getPath())) {
			return false;
		}
		Node node = this.getChildByPath(path);
		if (node != null) {
			Node parentNode = this.getChildByPath(node.getParentPath());
			parentNode.children.remove(node.name);
			return true;
		}
		return false;
	}
	
	/**
	 * 修改name
	 *
	 * @param newName    新的名称
	 * @param parentNode 父节点
	 */
	public void reviseName (String newName, Node parentNode) {
		parentNode.children.remove(this.name);
		this.name = newName;
		if (this.isDirectory()) {
			this.path = this.path.substring(0, this.path.lastIndexOf("/"));
			this.path = this.path.substring(0, this.path.lastIndexOf("/"));
			this.path += "/" + newName + "/";
			
			Stack<Node> stack = new Stack<>();
			for (Map.Entry<String, Node> entry : this.children.entrySet()) {
				stack.push(entry.getValue());
			}
			if (this.removeChild()) {
				while (! stack.isEmpty()) {
					Node node = stack.pop();
					this.copy(node);
				}
			}
		} else {
			this.path = this.path.substring(0, this.path.lastIndexOf("/"));
			this.path += "/" + newName;
		}
		
		parentNode.children.put(newName, this);
		
	}
	
	/**
	 * 复制一个节点到当前节点
	 *
	 * @param node 要复制节点
	 */
	public void copy (Node node) {
		if (node.isDirectory()) {
			String savePath = this.createChild(node.getName(), null, true, "Directory", null);
			Node saveNode = this.getChildByPath(savePath);
			for (Map.Entry<String, Node> entry : node.getChildren().entrySet()) {
				saveNode.copy(entry.getValue());
			}
		} else {
			this.createChild(node.getName(), node.getSHA_256(), node.isDirectory(), node.getType(), node.getSize());
		}
	}
	
	/**
	 * 将当前节点转换为JSON格式的字符串。
	 *
	 * @return 当前节点的JSON字符串表示
	 */
	public String toJson () {
		Gson gson = new GsonBuilder()
				            .registerTypeAdapter(Node.class, new NodeDeserializer())
//				            .setPrettyPrinting().create();
                            .create();
		return gson.toJson(this);
	}
	
	/**
	 * 从JSON字符串反序列化创建节点对象。
	 *
	 * @param json JSON字符串
	 * @return 反序列化后的节点对象
	 */
	public static Node fromJson (String json) {
		Gson gson = new GsonBuilder()
				            .registerTypeAdapter(Node.class, new NodeDeserializer())
				            .create();
		return gson.fromJson(json, Node.class);
	}
	
	/**
	 * 判断当前节点是否为目录。
	 *
	 * @return 如果当前节点是目录，则返回true；否则返回false。
	 */
	public abstract boolean isDirectory ();
	
	/**
	 * 判断当前节点是否为文件。
	 *
	 * @return 如果当前节点是文件，则返回true；否则返回false。
	 */
	public abstract boolean isFile ();
}
